#ifndef __HEAD_cuh__
#define __HEAD_cuh__

#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <math.h>
#include <time.h>
#include <cuda_runtime.h>
#include <cublas_v2.h>
#include <device_launch_parameters.h>
#include <sstream>
#include <curand.h>
#include <cusolverDn.h>

inline void checkSolver(cusolverStatus_t err, char const * file, unsigned int line){
	if ((err) != CUSOLVER_STATUS_SUCCESS){
		std::stringstream ss;
		ss << "CUSOLVER error " << err << " at " << file << ":" << line;
		printf("%s\n",ss.str().c_str());
		throw std::runtime_error(ss.str());
	}
}

inline void checkCudaRand(curandStatus_t err, char const * file, unsigned int line){
	if ((err) != CURAND_STATUS_SUCCESS){
		std::stringstream ss;
		ss << "CURAND error " << err << " at " << file << ":" << line;
		printf("%s\n", ss.str().c_str());
		throw std::runtime_error(ss.str());
	}
}

inline void checkCudaError(cudaError_t err, char const * file, unsigned int line){
	if (err != cudaSuccess){
		std::stringstream ss;
		ss << "CUDA error " << err << " " << cudaGetErrorString(err) << " at " << file << ":" << line;
		printf("%s\n", ss.str().c_str());
		throw std::runtime_error(ss.str());
	}
}

inline void check(bool bTrue, const char* msg, char const * file, unsigned int line){
	if (!bTrue){
		std::stringstream ss;
		ss << "Error: \"" << msg << "\" at " << file << ":" << line;
		printf("%s\n", ss.str().c_str());
		throw std::runtime_error(ss.str());
	}
}

inline void checkCudaStatus(cublasStatus_t status, char const * file, unsigned int line){
	if (status != CUBLAS_STATUS_SUCCESS){
		std::stringstream ss;
		ss << "CUDA status " << status << " at " << file << ":" << line;
		printf("%s\n", ss.str().c_str());
		throw std::runtime_error(ss.str());
	}
}

#define CHECK_PARAM(x, msg)   check((x), (msg), __FILE__, __LINE__)
#define CHECK_CUDA(cudaError) checkCudaError((cudaError), __FILE__, __LINE__)
#define CHECK_CUDA_STATUS(cudaStatus) checkCudaStatus((cudaStatus),__FILE__,__LINE__)
#define CHECK_CURAND(err) checkCudaRand((err),__FILE__,__LINE__)
#define CHECK_CUSOLVER(err) checkSolver((err),__FILE__,__LINE__)

//col-major and zero-based
//ld means leading-dimension which is alse the number of rows of matrix in this case
#define IDX2C(row,col,ld) (((col)*(ld))+(row))


//process dataset into 3 matrix: UIview, UIview_test and UUview
#define UIviewFileName "netflix_650thres_34630_17770.txt"
#define nUsers 34630
#define nItems 17770

//#define UIviewFileName "ml10m_50_20thres_43608_8940.txt"
//#define nUsers 43608
//#define nItems 8940

//#define UIviewFileName "small_ratings.txt"
//#define nUsers 5014
//#define nItems 5487

#define M_ADD_OPS 1
#define M_MINUS_OPS 2
#define M_TIMES_OPS 3
#define M_DIVIDE_OPS 4

struct Ele{
	float val;
	int pos;
	bool operator < (const Ele& rt)
	const {
		return this->val < rt.val;
	}
};

struct Params
{
	float split_rate;
	float maxS;
	float minS;
	int r;

	float alpha;
	float beta;
};

void params_to_string(Params &params, std::string &s);


bool read_data_2_matrix(float *UIview, float *UIview_test, 
	const float split_rate, float *IUview);

void DCF(
	float *B,/* out1: r X nusers*/
	float *D,/* out2: r X nitems*/
	const Params &params,
	float *h_S,
	float *h_ST);

void write_test_data(const float *UIview_test,
	const int m,
	const int n,
	const char *filename);
void write_into_file(float *B,
	int r,
	int n, 
	const char *filename);

#endif